package algebra;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


import kb.ABox;
import kb.DescriptionLogicsKB;
import kb.TBox;
import utils.Util;
import utils.exception.OrderingDescriptionException;
import datatableaux.Reason;
import entities.ConceptAssertion;
import entities.concept.AndConcept;
import entities.concept.Concept;
import entities.concept.DomainConceptEQ;
import entities.concept.DomainConceptLT;
import entities.concept.NotConcept;
import entities.concept.OrConcept;
import entities.role.Role;
import entities.vocabulary.KBVocabulary;
import entities.vocabulary.QueryKeywords;

public class OrderingDescription {	
	private static boolean ENABLE_OPTIMIZATIONS = true;
	private static Map<Concept, Concept> subsumptionCache = new HashMap<Concept, Concept>();

	private static int cacheHits = 0;
	private static int structuralSubsumptions = 0;
	
//	private TBox tb;
//	private ABox ab;
	private DescriptionLogicsKB kb;
	private String type = "";

	private String od;
	public OrderingDescription residualOd;
	public OrderingDescription residualOd2;
	public String arg;
	public Concept partition;

	public OrderingDescription(String od, DescriptionLogicsKB kb) throws OrderingDescriptionException {
		this.od = od;
		this.kb = kb;
//		this.tb = kb.getTbox();
//		this.ab = kb.getAbox();
		if(!this.od.trim().equals("")){
			parse(this.od);
		}else{
			//this Od will be created by tokens
			this.residualOd = null;
			this.residualOd2 = null;
			this.arg = null;
			this.partition = null;
		}
	}

	private void parse(String od) throws OrderingDescriptionException {
		od = od.trim();
//		System.out.println("PARSING: " + od);
		if (!od.startsWith(KBVocabulary.LEFTDEL) && !od.startsWith(QueryKeywords.ORDERING_UN)) {
			throw new OrderingDescriptionException("Malformed Ordering Description: " + od);
		}

		//top level Un: ordering by the individual names
		if (od.equalsIgnoreCase(QueryKeywords.ORDERING_UN) || od.equalsIgnoreCase("(un)")) {
			setType(QueryKeywords.ORDERING_UN);		
			return;
		}

		int space = od.indexOf(' ');
		String op = od.substring(1, space);
		int space2 = od.indexOf(' ', space + 1);
		if(op.compareToIgnoreCase(QueryKeywords.ORDERING_FEATURE) == 0){
			this.arg = od.substring(space, space2).trim();
			this.residualOd = new OrderingDescription(od.substring(space2, od.length() - 1), this.kb);
		}else if(op.compareToIgnoreCase(QueryKeywords.ORDERING_PARTITION) == 0){
			if (od.charAt(space + 1) == '(') {
				space2 = space + findCloseParen(od.substring(space)) + 2;
			}
			else {
				space2 = od.indexOf(' ', space + 1) + 1;
			}
//			System.out.println("PARTITIONSTR: " + od.substring(space, space2).trim());
			this.partition = Util.parseConcept(od.substring(space, space2).trim());
			space = space2;
			int idx = 0;
			if (od.charAt(space) == '(') {
				idx = space + findCloseParen(od.substring(space));
			}
			else {
				idx = od.indexOf(' ', space);
			}

			residualOd = new OrderingDescription(od.substring(space, idx), kb);
			residualOd2 = new OrderingDescription(od.substring(idx + 1, od.length() - 1), kb);
		}else {
			throw new OrderingDescriptionException("Malformed Ordering Description: " + od);
		}
	}
	
	private int findCloseParen(String od) {
		od = od.trim();
		int open = 0;
		int i = 0;
		for ( ; i < od.length(); i++) {
			char c = od.charAt(i);
			if (c == ')') open--;
			else if (c == '(') open++;

			if (open == 0) {
				return i + 1;
			}
		}
		return -1;
	}


	//by default, un is assumed.
	public int compare(ConceptAssertion ca, ConceptAssertion ca2) throws OrderingDescriptionException {
		if(getType().compareToIgnoreCase(QueryKeywords.ORDERING_FEATURE)==0){
			return compareFeature(ca, ca2);
		}else if(getType().compareToIgnoreCase(QueryKeywords.ORDERING_PARTITION)==0){
			return comparePartition(ca, ca2);
		}else{
			// compare on individual names
			return ca.getIndividual().getName().compareTo(ca2.getIndividual().getName());
//			List<Concept> d1 = structuralFindK(ca, DescriptionIndex.ID_FEATURE_NAME);
//			List<Concept> d2 = structuralFindK(ca2, DescriptionIndex.ID_FEATURE_NAME);
//			if (d1.size() == 0 || d2.size() == 0) {
//				return 0;
//			}
//			return ((DomainConcept<Role, String>)d1.get(0)).getValue().compareTo(((DomainConcept<Role, String>)d2.get(0)).getValue());

		}
	}

	
	private int compareFeature(ConceptAssertion ca, ConceptAssertion ca2) throws OrderingDescriptionException {

		
		if (ENABLE_OPTIMIZATIONS) {
			//check for syntactic feature values first
			List<Concept> k1List = structuralFindK(ca, this.arg);
			if (!k1List.isEmpty()) {
				if (k1List.size() > 1) {
					throw new OrderingDescriptionException("Concept not sufficiently descriptive");
				}
				String k = (String) ((DomainConceptEQ<?, ?>)(k1List.get(0))).getValue();

				List<Concept> k2List = structuralFindK(ca2, this.arg);
				if (!k2List.isEmpty()) {
					if (k2List.size() > 1) {
						throw new OrderingDescriptionException("Concept not sufficiently descriptive");
					}
					String k2 = (String) ((DomainConceptEQ<?, ?>)(k2List.get(0))).getValue();

					setStructuralSubsumptions(getStructuralSubsumptions() + 1);
					return k.compareTo(k2);
				}
			}
		}
		Concept lhs = new AndConcept(ca.getConcept(), ca2.getConcept().getPrimeCopy());
		Concept rhs = new DomainConceptLT<Role, Role>(new Role(this.arg), new Role(this.arg + Concept.PRIME));
		System.out.println("subsumption test: "+lhs+"; "+rhs);
		Reason reason = new Reason(false);
		if(reason.TBoxSubsumptionCheck(lhs, rhs, kb)){
			return -1;
		}
		rhs = new DomainConceptLT<Role, Role>(new Role(this.arg + Concept.PRIME), new Role(this.arg));
		if(reason.TBoxSubsumptionCheck(lhs, rhs, kb)){
			return -1;
		}
		rhs = new DomainConceptEQ<Role, Role>(new Role(this.arg + Concept.PRIME), new Role(this.arg));
		if(reason.TBoxSubsumptionCheck(lhs, rhs, kb)){
			return this.residualOd.compare(ca, ca2);
		}
//		if (Util.subsumes(lhs, rhs, tb)) {	
//			return -1;
//		}
//		rhs = new DomainConceptLT<Role, Role>(new Role(this.arg + Concept.PRIME), new Role(this.arg));
//		if (Util.subsumes(lhs, rhs, tb)) {
//			return 1;
//		}

//		rhs = new DomainConceptEQ<Role, Role>(new Role(this.arg + Concept.PRIME), new Role(this.arg));
//		if (Util.subsumes(lhs, rhs, tb)) {
//			return this.residualOd.compare(ca, ca2);
//		}

		return 0;
	}

	private List<Concept> structuralFindK(ConceptAssertion ca, String f) {
		LinkedList<Concept> result = new  LinkedList<Concept>();
		LinkedList<Concept> q = new  LinkedList<Concept>();
		q.add(ca.getConcept());
		while (!q.isEmpty()) {
			Concept con = q.remove();
			if (con instanceof AndConcept) {
				for (Concept and : ((AndConcept)con).getConceptSet()) {
					q.add(and);
				}
			}
			else if (con instanceof OrConcept) {
				for (Concept or : ((OrConcept)con).getConceptSet()) {
					q.add(or);
				}
			}

			if (con instanceof DomainConceptEQ) {
				DomainConceptEQ<?,?> bk = (DomainConceptEQ<?,?>) con;
				if (bk.getFeature().toString().equals(f)) {
					// Debug
//					Object val = ((DomainConceptEQ) con).getValue();
//					Object feat = ((DomainConceptEQ) con).getFeature();
//					System.err.println("FOUND FEATURE: " + feat + " = " + val);
					// ---
					result.add(con);
				}
			}
		}
		return result;
	}
	private int comparePartition(ConceptAssertion ca, ConceptAssertion ca2) throws OrderingDescriptionException {
		Concept notPartition = new NotConcept(this.partition);
		if (OrderingDescription.subsumes(ca.getConcept(), this.partition, kb)) {
			if (OrderingDescription.subsumes(ca2.getConcept(), this.partition, kb)) {
				return this.residualOd.compare(ca, ca2);
			}
			else if (OrderingDescription.subsumes(ca2.getConcept(), notPartition, kb)) {	
				return -1;
			}
		}
		else if (OrderingDescription.subsumes(ca.getConcept(), notPartition,kb)) {
			if (OrderingDescription.subsumes(ca2.getConcept(), this.partition, kb)) {
				return 1;
			}
			else if (OrderingDescription.subsumes(ca2.getConcept(), notPartition, kb)) {
				return this.residualOd2.compare(ca, ca2);
			}
		}
		
		return 0;
	}

//	public void setTBox(TBox tb) {
//		this.tb = tb;
//	}
//	
//	public void setABox(ABox ab){
//		this.ab = ab;
//	}

	public ABox getABox(){
		return this.kb.getAbox();
	}
	public TBox getTBox() {
		return this.kb.getTbox();
	}
	
	public DescriptionLogicsKB getKB(){
		return this.kb;
	}
	
	public void setKB(DescriptionLogicsKB mykb){
		this.kb = mykb;
	}
	
	//test if T |= a under b, as T and i:a |= i:b
	
	
	private static boolean subsumes(Concept a, Concept b, DescriptionLogicsKB kb) {
		Reason res = new Reason(false);
		if (!ENABLE_OPTIMIZATIONS) {
			return res.TBoxSubsumptionCheck(a, b, kb);
		}
		if (subsumptionCache.get(a) == b) {
			setCacheHits(getCacheHits() + 1);
			return true;
		}
		if (res.TBoxSubsumptionCheck(a, b, kb)) {
			subsumptionCache.put(a, b);
			return true;
		}
		return false;
	}

	@Override
	public String toString() {
		String res = "";
		if(this.partition == null){
			//not a part od
			if(this.residualOd == null){
				if(this.arg == null){
					//not (f un)
					res += "[un]";
				}else{
					res += "["+arg+" un]";
				}
			}else{
				// f od
				res += "["+this.arg+ " "+this.residualOd.toString()+"]";
			}
		}else{
			//C (od1 d2)
			res += "["+this.partition.toString()+" ("+this.residualOd.toString()+", "
			+this.residualOd2.toString()+")]";
		}
		return res;
	}

	public static void setCacheHits(int cacheHits) {
		OrderingDescription.cacheHits = cacheHits;
	}

	public static int getCacheHits() {
		return cacheHits;
	}

	public static void setStructuralSubsumptions(int structuralSubsumptions) {
		OrderingDescription.structuralSubsumptions = structuralSubsumptions;
	}

	public static int getStructuralSubsumptions() {
		return structuralSubsumptions;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getType() {
		return type;
	}

}
